// Package logging provides structured logging functionality for smart snapshotter
package logging

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/smart-snapshotter/pkg/types"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// Logger interface defines the logging contract
type Logger interface {
	Debug(msg string, fields ...Field)
	Info(msg string, fields ...Field)
	Warn(msg string, fields ...Field)
	Error(msg string, fields ...Field)
	Fatal(msg string, fields ...Field)

	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})

	WithContext(ctx context.Context) Logger
	WithFields(fields ...Field) Logger
	WithComponent(component string) Logger

	Sync() error
}

// Field represents a structured logging field
type Field struct {
	Key   string
	Value interface{}
}

// StringField creates a string field
func StringField(key, value string) Field {
	return Field{Key: key, Value: value}
}

// IntField creates an int field
func IntField(key string, value int) Field {
	return Field{Key: key, Value: value}
}

// Int64Field creates an int64 field
func Int64Field(key string, value int64) Field {
	return Field{Key: key, Value: value}
}

// BoolField creates a bool field
func BoolField(key string, value bool) Field {
	return Field{Key: key, Value: value}
}

// DurationField creates a duration field
func DurationField(key string, value time.Duration) Field {
	return Field{Key: key, Value: value}
}

// ErrorField creates an error field
func ErrorField(err error) Field {
	return Field{Key: "error", Value: err}
}

// ComponentField creates a component field
func ComponentField(component string) Field {
	return Field{Key: "component", Value: component}
}

// zapLogger implements Logger interface using zap
type zapLogger struct {
	logger *zap.Logger
	config *types.LoggingConfig
	fields []Field
	mu     sync.RWMutex
}

// NewLogger creates a new logger instance
func NewLogger(config *types.LoggingConfig) (Logger, error) {
	if config == nil {
		config = &types.LoggingConfig{
			Level:  "info",
			Format: "json",
			Output: "stdout",
		}
	}

	zapConfig := zapcore.EncoderConfig{
		TimeKey:        "timestamp",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "message",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	var encoder zapcore.Encoder
	switch config.Format {
	case "json":
		encoder = zapcore.NewJSONEncoder(zapConfig)
	case "text":
		encoder = zapcore.NewConsoleEncoder(zapConfig)
	default:
		encoder = zapcore.NewJSONEncoder(zapConfig)
	}

	level, err := parseLogLevel(config.Level)
	if err != nil {
		return nil, fmt.Errorf("invalid log level: %w", err)
	}

	var writer zapcore.WriteSyncer
	switch config.Output {
	case "stdout":
		writer = zapcore.AddSync(os.Stdout)
	case "stderr":
		writer = zapcore.AddSync(os.Stderr)
	case "file":
		if config.FilePath == "" {
			return nil, fmt.Errorf("file path is required when output is file")
		}

		// Create log directory if it doesn't exist
		dir := filepath.Dir(config.FilePath)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return nil, fmt.Errorf("failed to create log directory: %w", err)
		}

		file, err := os.OpenFile(config.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if err != nil {
			return nil, fmt.Errorf("failed to open log file: %w", err)
		}
		writer = zapcore.AddSync(file)
	default:
		writer = zapcore.AddSync(os.Stdout)
	}

	core := zapcore.NewCore(encoder, writer, level)

	options := []zap.Option{
		zap.AddCaller(),
		zap.AddCallerSkip(1),
	}

	if config.Level == "debug" {
		options = append(options, zap.AddStacktrace(zapcore.ErrorLevel))
	}

	logger := zap.New(core, options...)

	return &zapLogger{
		logger: logger,
		config: config,
		fields: make([]Field, 0),
	}, nil
}

// parseLogLevel parses log level string to zapcore.Level
func parseLogLevel(level string) (zapcore.Level, error) {
	switch strings.ToLower(level) {
	case "debug":
		return zapcore.DebugLevel, nil
	case "info":
		return zapcore.InfoLevel, nil
	case "warn", "warning":
		return zapcore.WarnLevel, nil
	case "error":
		return zapcore.ErrorLevel, nil
	case "fatal":
		return zapcore.FatalLevel, nil
	default:
		return zapcore.InfoLevel, fmt.Errorf("unknown log level: %s", level)
	}
}

// Debug logs a debug message
func (l *zapLogger) Debug(msg string, fields ...Field) {
	l.log(zapcore.DebugLevel, msg, fields...)
}

// Info logs an info message
func (l *zapLogger) Info(msg string, fields ...Field) {
	l.log(zapcore.InfoLevel, msg, fields...)
}

// Warn logs a warning message
func (l *zapLogger) Warn(msg string, fields ...Field) {
	l.log(zapcore.WarnLevel, msg, fields...)
}

// Error logs an error message
func (l *zapLogger) Error(msg string, fields ...Field) {
	l.log(zapcore.ErrorLevel, msg, fields...)
}

// Fatal logs a fatal message
func (l *zapLogger) Fatal(msg string, fields ...Field) {
	l.log(zapcore.FatalLevel, msg, fields...)
}

// Debugf logs a formatted debug message
func (l *zapLogger) Debugf(format string, args ...interface{}) {
	l.Debug(fmt.Sprintf(format, args...))
}

// Infof logs a formatted info message
func (l *zapLogger) Infof(format string, args ...interface{}) {
	l.Info(fmt.Sprintf(format, args...))
}

// Warnf logs a formatted warning message
func (l *zapLogger) Warnf(format string, args ...interface{}) {
	l.Warn(fmt.Sprintf(format, args...))
}

// Errorf logs a formatted error message
func (l *zapLogger) Errorf(format string, args ...interface{}) {
	l.Error(fmt.Sprintf(format, args...))
}

// Fatalf logs a formatted fatal message
func (l *zapLogger) Fatalf(format string, args ...interface{}) {
	l.Fatal(fmt.Sprintf(format, args...))
}

// log is the internal logging method
func (l *zapLogger) log(level zapcore.Level, msg string, fields ...Field) {
	l.mu.RLock()
	allFields := append(l.fields, fields...)
	l.mu.RUnlock()

	zapFields := make([]zap.Field, 0, len(allFields))
	for _, field := range allFields {
		zapFields = append(zapFields, zap.Any(field.Key, field.Value))
	}

	// Add caller information for debug level
	if level == zapcore.DebugLevel && l.config.Level == "debug" {
		_, file, line, ok := runtime.Caller(2)
		if ok {
			// Get relative path from project root
			if idx := strings.Index(file, "smart-snapshotter"); idx != -1 {
				file = file[idx+len("smart-snapshotter")+1:]
			}
			zapFields = append(zapFields, zap.String("caller", fmt.Sprintf("%s:%d", file, line)))
		}
	}

	switch level {
	case zapcore.DebugLevel:
		l.logger.Debug(msg, zapFields...)
	case zapcore.InfoLevel:
		l.logger.Info(msg, zapFields...)
	case zapcore.WarnLevel:
		l.logger.Warn(msg, zapFields...)
	case zapcore.ErrorLevel:
		l.logger.Error(msg, zapFields...)
	case zapcore.FatalLevel:
		l.logger.Fatal(msg, zapFields...)
	}
}

// WithContext creates a logger with context fields
func (l *zapLogger) WithContext(ctx context.Context) Logger {
	fields := make([]Field, 0)

	// Add request ID if available
	if requestID := ctx.Value("request_id"); requestID != nil {
		fields = append(fields, StringField("request_id", requestID.(string)))
	}

	// Add trace ID if available
	if traceID := ctx.Value("trace_id"); traceID != nil {
		fields = append(fields, StringField("trace_id", traceID.(string)))
	}

	// Add user ID if available
	if userID := ctx.Value("user_id"); userID != nil {
		fields = append(fields, StringField("user_id", userID.(string)))
	}

	return l.WithFields(fields...)
}

// WithFields creates a logger with additional fields
func (l *zapLogger) WithFields(fields ...Field) Logger {
	l.mu.Lock()
	defer l.mu.Unlock()

	newFields := make([]Field, len(l.fields)+len(fields))
	copy(newFields, l.fields)
	copy(newFields[len(l.fields):], fields)

	return &zapLogger{
		logger: l.logger,
		config: l.config,
		fields: newFields,
	}
}

// WithComponent creates a logger with component field
func (l *zapLogger) WithComponent(component string) Logger {
	return l.WithFields(ComponentField(component))
}

// Sync flushes any buffered log entries
func (l *zapLogger) Sync() error {
	return l.logger.Sync()
}

// Global logger instance
var (
	globalLogger Logger
	loggerOnce   sync.Once
)

// InitGlobalLogger initializes the global logger
func InitGlobalLogger(config *types.LoggingConfig) error {
	var err error
	loggerOnce.Do(func() {
		globalLogger, err = NewLogger(config)
	})
	return err
}

// GetLogger returns the global logger
func GetLogger() Logger {
	if globalLogger == nil {
		// Create a default logger if not initialized
		logger, _ := NewLogger(nil)
		globalLogger = logger
	}
	return globalLogger
}

// Convenience functions for global logger

// Debug logs a debug message using the global logger
func Debug(msg string, fields ...Field) {
	GetLogger().Debug(msg, fields...)
}

// Info logs an info message using the global logger
func Info(msg string, fields ...Field) {
	GetLogger().Info(msg, fields...)
}

// Warn logs a warning message using the global logger
func Warn(msg string, fields ...Field) {
	GetLogger().Warn(msg, fields...)
}

// Error logs an error message using the global logger
func Error(msg string, fields ...Field) {
	GetLogger().Error(msg, fields...)
}

// Fatal logs a fatal message using the global logger
func Fatal(msg string, fields ...Field) {
	GetLogger().Fatal(msg, fields...)
}

// Debugf logs a formatted debug message using the global logger
func Debugf(format string, args ...interface{}) {
	GetLogger().Debugf(format, args...)
}

// Infof logs a formatted info message using the global logger
func Infof(format string, args ...interface{}) {
	GetLogger().Infof(format, args...)
}

// Warnf logs a formatted warning message using the global logger
func Warnf(format string, args ...interface{}) {
	GetLogger().Warnf(format, args...)
}

// Errorf logs a formatted error message using the global logger
func Errorf(format string, args ...interface{}) {
	GetLogger().Errorf(format, args...)
}

// Fatalf logs a formatted fatal message using the global logger
func Fatalf(format string, args ...interface{}) {
	GetLogger().Fatalf(format, args...)
}

// WithContext creates a logger with context using the global logger
func WithContext(ctx context.Context) Logger {
	return GetLogger().WithContext(ctx)
}

// WithFields creates a logger with fields using the global logger
func WithFields(fields ...Field) Logger {
	return GetLogger().WithFields(fields...)
}

// WithComponent creates a logger with component using the global logger
func WithComponent(component string) Logger {
	return GetLogger().WithComponent(component)
}

// Sync flushes the global logger
func Sync() error {
	return GetLogger().Sync()
}